/*******************************************************************
 *
 * Copyright (C), 2021-2022, LS, All rights reserved.
 *
 * Author      : 
 * Create Time : 2021-09-22
 * Version     : v0.1
 * Description : 
 *******************************************************************/
 
/*----------------------------Head file----------------------------*/
#include "OSAL.h"
#include "clock.h"
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include "osal_app.h"
#include "app_time.h"
#include "app_reset.h"
#include "user_config.h"
#include "bsp_hw_config.h"
#include "app_global_data.h"

#define APP_TIME_LOG_ENABLE 1

#ifdef APP_TIME_LOG_ENABLE
#include "log.h"
#define APP_TIME_LOG        LOG
#else
#define APP_TIME_LOG(...)
#endif

/*----------------------------macro file---------------------------*/

#if(USE_EXTERNAL_32K)
	#define USE_SYS_TICK    FASLE  //default use RTC, if system use RC32K as RTC clock source, system tick is usually recommended
#else
	#define USE_SYS_TICK    TRUE
#endif

#if(USE_SYS_TICK)
	#define RTC_CNT_RANGE   0x100000000
	#define TM_RATE (1000000/625)
#else
	#define RTC_CNT_RANGE   0x1000000
	#define RTC_CNT_COMPENSATE  0
	#define TM_RATE (32768)
#endif

#define NEG_TIMEZONE_MASK       0x8000


#define APP_DT_TICK_TIMER_START()     osal_start_timerEx(osalApp_TaskID, APP_DT_TICK_TIMER_EVT, DATETIME_SYNC_INTERVAL)
 
/*----------------------------type define--------------------------*/

/*----------------------------var define---------------------------*/
static const uint16_t days_2_nonleap[12] = {
    31,// Jan
    31+28,// Feb
    31+28+31,// Mar
    31+28+31+30, // Apr
    31+28+31+30+31,//5
    31+28+31+30+31+30,//6
    31+28+31+30+31+30+31,//7
    31+28+31+30+31+30+31+31,//8
    31+28+31+30+31+30+31+31+30,//9
    31+28+31+30+31+30+31+31+30+31,//10
    31+28+31+30+31+30+31+31+30+31+30,//11
    31+28+31+30+31+30+31+31+30+31+30+31,//12
};

static const uint16_t days_2_leap[12] = {
    31, // Jan
    31+29, // Feb
    31+29+31, // Mar
    31+29+31+30,// Apr
    31+29+31+30+31,//5
    31+29+31+30+31+30,//6
    31+29+31+30+31+30+31,//7
    31+29+31+30+31+30+31+31,//8
    31+29+31+30+31+30+31+31+30,//9
    31+29+31+30+31+30+31+31+30+31,//10
    31+29+31+30+31+30+31+31+30+31+30,//11
    31+29+31+30+31+30+31+31+30+31+30+31,//12
};

static datetime_cfg_t s_stm_cfg;
extern uint32_t osal_sys_tick;

/*-------------------------func declaration------------------------*/
bool convert_utc_2_rtc(uint32_t utc_time, struct tm *pTime);

static void app_datetime_adjust_baseline(struct tm* datetm);

/*-----------------------------------------------------------------*/


void app_time_set_dt_cfg(datetime_cfg_t *cfg)
{
    osal_sys_tick = cfg->snapshot;      //Need to set osal_sys_tick, or it may overflow and tm_base add more RTC_CNT_RANGE, then time is inaccurate.
    s_stm_cfg.tm_base = cfg->tm_base;
    s_stm_cfg.snapshot = cfg->snapshot;
    APP_DT_TICK_TIMER_START();
}


uint64_t app_time_tim_base_get(void)
{
    return s_stm_cfg.tm_base;
}


uint32_t app_time_snapshot_get(void)
{
    return s_stm_cfg.snapshot;
}


static void timer_cnt_get(uint32_t* tick)
{
#if(USE_SYS_TICK)
    *tick = hal_systick();
#else
    *tick = rtc_get_counter();      // read current RTC counter
#endif
}

#if (DEBUG_INFO == 1)
void app_datetime_get(app_time_rtc_t* pdtm)
{
    uint32_t ticks;
    struct tm * ptm = NULL;

    time_t tm = (time_t)(s_stm_cfg.tm_base / TM_RATE);
    timer_cnt_get(&ticks);
    if(s_stm_cfg.snapshot > ticks)
        tm += RTC_CNT_RANGE/TM_RATE;
    tm += ticks/TM_RATE;

    ptm = localtime(&tm);

    TM2DTM(pdtm, ptm)
}

void datetime_print(void)
{
    app_time_rtc_t dtm;
    app_datetime_get(&dtm);
    APP_TIME_LOG("Time is %d-%d-%d, %d:%d:%d\n", dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minutes, dtm.seconds);
}
#endif

void app_datetime_sync_handler(void)
{
    uint32_t tick = 0;
    timer_cnt_get(&tick);
    if(tick < s_stm_cfg.snapshot) {
#if(USE_SYS_TICK)
        s_stm_cfg.tm_base += RTC_CNT_RANGE;
#else
        s_stm_cfg.tm_base += (RTC_CNT_RANGE - RTC_CNT_COMPENSATE);
#endif
    }
    s_stm_cfg.snapshot = tick;
    APP_DT_TICK_TIMER_START();
#if (DEBUG_INFO == 1)
    datetime_print();
#endif
}

static void app_datetime_adjust_baseline(struct tm* datetm)
{
    uint64_t tmstamp;
    uint32_t ticks;
    timer_cnt_get(&ticks);
    tmstamp = (uint64_t)mktime(datetm);
    APP_TIME_LOG("%x.\n", (uint32_t)tmstamp);
    tmstamp = tmstamp*TM_RATE;
    s_stm_cfg.tm_base = tmstamp - ticks;
    s_stm_cfg.snapshot = ticks;
    APP_DT_TICK_TIMER_START();
}


void app_datetime_set(app_time_rtc_t dtm)
{
    struct tm tm;
    memset(&tm, 0, sizeof(tm));
    DTM2TM(&tm, &dtm);
    APP_TIME_LOG("Time is %d-%d-%d, %d:%d:%d\n", dtm.year, dtm.month, dtm.day, dtm.hour, dtm.minutes, dtm.seconds);
    app_datetime_adjust_baseline(&tm);
}


void app_set_default_datetime(void)
{
    app_time_rtc_t rtc;
    rtc.year = 2000;
    rtc.month = 1;
    rtc.day = 1;
    rtc.hour = 0;
    rtc.minutes = 0;
    rtc.seconds = 0;
    app_datetime_set(rtc);
    //APP_TIME_LOG("set_default_datetime: %u.\n", (time_t)(s_stm_cfg.tm_base/TM_RATE));
}


uint32_t trans_to_local_utc(uint32_t uni_utc)
{
    uint16_t timezone_mins = g_app_data.user.timezone_mins;
    uint32_t local_utc = 0;

    if(timezone_mins < NEG_TIMEZONE_MASK) {
        local_utc = uni_utc + timezone_mins*60;
    }
    else {
        local_utc = uni_utc - (timezone_mins - NEG_TIMEZONE_MASK)*60;
    }

    return local_utc;
}


uint32_t trans_to_universal_utc(uint32_t local_utc)
{
    uint16_t timezone_mins = g_app_data.user.timezone_mins;
    uint32_t uni_utc = 0;

    if(timezone_mins < NEG_TIMEZONE_MASK) {
        uni_utc = local_utc - timezone_mins*60;
    }
    else {
        uni_utc = local_utc + (timezone_mins - NEG_TIMEZONE_MASK)*60;
    }

    return uni_utc;
}


void app_time_uni_utc_set(uint32_t uni_utc)
{
    struct tm tm;
    memset((uint8_t*)&tm, 0, sizeof(tm));
    uint32_t local_utc = trans_to_local_utc(uni_utc);
    convert_utc_2_rtc(local_utc, &tm);
    app_datetime_adjust_baseline(&tm);
#if (DEBUG_INFO == 1)
    datetime_print();
#endif
}


uint32_t app_time_universal_utc_get(void)
{
    uint32_t ticks;

    time_t tm = (time_t)(s_stm_cfg.tm_base / TM_RATE);
    timer_cnt_get(&ticks);
    if(s_stm_cfg.snapshot > ticks)
        tm += RTC_CNT_RANGE/TM_RATE;
    tm += ticks/TM_RATE;

    tm = trans_to_universal_utc(tm);
    APP_TIME_LOG("uni utc: %u.\n", tm);
    return tm;
}


uint64_t app_time_base_get(void)
{
    //Time base < 2019.10.1 0:0:0
    if(((time_t)(s_stm_cfg.tm_base/TM_RATE)) < VALID_START_UTC) {
        APP_TIME_LOG("Not a valid utc time.\n");
        return 0;
    }

    //RTC_LOG("app_system_reset.\n");
    app_datetime_sync_handler();

    return s_stm_cfg.tm_base;
}


/** Common **/
bool is_leap_year(uint16_t year)
{
    if( year % 4 )
        return false;

    if( year % 100 )
        return true;

    if( year % 400 )
        return false;
    else
        return true;
}

bool convert_utc_2_rtc(uint32_t utc_time, struct tm *pTime)
{
    int i;
    unsigned int days;
    unsigned short year = 1970;
    if( pTime == NULL )
    {
        return false;
    }

    if( utc_time > 4102329600ul ) /* 2099/12/31 23:59 set the max time */
    {
        return false;
    }

    pTime->tm_sec = (utc_time % 60);
    utc_time = (utc_time / 60);

    pTime->tm_min = (utc_time % 60);
    utc_time = (utc_time / 60);

    pTime->tm_hour = (utc_time % 24 );
    days = utc_time = ( utc_time / 24 );

    pTime->tm_wday = (4 + (days % 7) ) % 7;

    while( days > 365 )
    {
        if(is_leap_year(year))
            days--;
        days -= 365;
        year++;
    }
    //now days <= 365
    if( !is_leap_year(year) && (days == 365) )
    {
        year++;
        pTime->tm_mon = 1;
        pTime->tm_mday = 1;
        
        pTime->tm_year = year - 1900;
        pTime->tm_mon -= 1;
        pTime->tm_wday -= 1;
        return true;
    }
    pTime->tm_year = year;
    //here days <365
    if( is_leap_year(year) )
    {
        for (i = 0; i < 12; i++)
        {
            if (days < days_2_leap[i])
            {
                pTime->tm_mon = i+1;
                if(i == 0)
                {
                    pTime->tm_mday = days;
                }
                else
                {
                    pTime->tm_mday = days - days_2_leap[i-1];
                }
                pTime->tm_mday++;
                break;
            }
        }
    }
    else
    {
        for(i = 0; i < 12; i++)
        {
            if(days < days_2_nonleap[i])
            {
                pTime->tm_mon = i+1;
                if(i == 0)
                {
                    pTime->tm_mday = days;
                }
                else
                {
                    pTime->tm_mday = days - days_2_nonleap[i-1];
                }
                pTime->tm_mday++;
                break;
            }
        }
    }

    pTime->tm_year = year - 1900;
    pTime->tm_mon -= 1;
    pTime->tm_wday -= 1;

    return true;
}
